// (C) 2021 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

//define avmm encoded events here
`define AVMM_IDLE                 8'h00
`define AVMM_WR_REQ               8'h01
`define AVMM_RD_REQ               8'h02
`define AVMM_WR_CMPL              8'h04
`define AVMM_RD_CMPL              8'h08
`define AVMM_CRC_ERR              8'h10
`define INVALID_ACCESS_CPU        8'h20
`define INVALID_ACCESS_DEBUG      8'h40

module lvds_mm_sm_agent
    #(
        parameter  DATA_WIDTH    = 32,
        parameter  ADDR_WIDTH    = 16,
        parameter  TIMEOUT_PARAM = 25000
    ) 
(
    input                           clk,
    input                           reset,
    
    //Master Agent interface, Only exist on slave side
    output reg [ADDR_WIDTH-1:0]     mstr_agent_address,
    output reg                      mstr_agent_read,
    output reg                      mstr_agent_write,
    output reg [DATA_WIDTH-1:0]     mstr_agent_writedata,
    output reg [(DATA_WIDTH/8)-1:0] mstr_agent_byteenable, //always 4'b1111
    input      [DATA_WIDTH-1:0]     mstr_agent_readdata,
    input                           mstr_agent_readdatavalid,
    input                           mstr_agent_waitrequest,
        
    //Encoding AVMM events to ioc frame
    output reg  [7:0]               ioc_frame_o,
    input       [7:0]               ioc_frame_i,
    //tx/rx frame cnt
    input       [3:0]               tx_frm_offset,
    input       [3:0]               rx_frm_offset,
	 //input from channel_ctrl
	 input                           frame_loss_crc_A2H,   //Agent receives frame_loss_crc indication from host that A2H direction has error, id_bit6
	 input                           frame_aligned_tx,
	 input                           frame_loss_crc_H2A,   //Agent detects frame_loss_crc indication from host by Rx detection that H2A direction has error
	 //input from AVMM slave -- CPU/Debug FPGA CSRs
	 input                           invalid_access_cpu,
	 input                           invalid_access_dbg,
    
    //Sideband Signals
	 output reg  [3:0]               status_reg,       //status register to log all status during IIC_MM transaction
	 
    output reg                      crc_check_pass,   //1'b1: Pass; 1'b0: Fail.
    output                          avmm_timeout,
    output reg  [31:0]              crc_err_cnt,      //Record how many crc_error has been happened
    output reg  [31:0]              timeout_cnt       //Record how many timeout event has been happened, can't see readvalid.    
);

    localparam IDLE                  = 8'h00;
    localparam TX_WR_CMPL            = 8'h01;    
    localparam RX_ADDR_B1            = 8'h02;
    localparam RX_ADDR_B0            = 8'h03;
    localparam RX_DATA_B3            = 8'h04;
    localparam RX_DATA_B2            = 8'h05;
    localparam RX_DATA_B1            = 8'h06;
    localparam RX_DATA_B0            = 8'h07;
    localparam CRC_SEND              = 8'h08;
    localparam CRC_CLR_WAIT          = 8'h09;
    localparam PREPARE_CMPL          = 8'h0A;
    localparam TX_DATA_B3            = 8'h0B;
    localparam TX_DATA_B2            = 8'h0C;
    localparam TX_DATA_B1            = 8'h0D;
    localparam TX_DATA_B0            = 8'h0E;
    localparam CRC_CHECK             = 8'h0F;
    localparam TX_RD_CMPL            = 8'h10;
    localparam ASSERT_AVMM           = 8'h11;
	 localparam CRC_FAIL_LOCAL        = 8'h12;
	 localparam INVALID_ACCESS_CPU    = 8'h13;
	 localparam INVALID_ACCESS_DEBUG  = 8'h14;

reg  [ 7:0]  crc_data_in;     //bytes used to update CRC value
wire [15:0]  crc_out;         //CRC result    

reg  [ 7:0]  state;
reg          write_cmd_store; //store if current transaction is AVMM write
reg          read_cmd_store;  //store if current transaction is AVMM read
reg  [15:0]  address_hold; 
reg  [31:0]  writedata_hold;
reg  [31:0]  readdata_hold;
reg          crc_gen_clr;
reg          tx_crc_cal;       //Indicate current tx_byte should be used to update CRC
reg          rx_crc_cal;       //Indicate current rx_byte should be used to update CRC
reg  [15:0]  crc_in;           //Initial and Previous crc value
reg  [15:0]  avmm_timer;

//Control State Machine
always @ (posedge clk) begin
    if (reset) begin
        ioc_frame_o           <= `AVMM_IDLE; //Send IDLE on the frame when no transaction.    
        write_cmd_store       <= 1'b0;
        read_cmd_store        <= 1'b0;
        address_hold          <= 16'b0;
        writedata_hold        <= 16'b0;
        readdata_hold         <= 16'b0;
        crc_gen_clr           <= 1'b1; // clear crc initial value
        crc_err_cnt           <= 16'b0;
        timeout_cnt           <= 16'b0;
        mstr_agent_address    <= 'b0;
        mstr_agent_read       <= 1'b0;
        mstr_agent_write      <= 1'b0;
        mstr_agent_writedata  <= 'b0;
        mstr_agent_byteenable <= 4'b1111; 
        crc_check_pass        <= 1'b1;
        status_reg            <= 4'b0;        
        state                 <= IDLE;
    end 
	 else begin
        case (state)
            IDLE: begin            
                if (tx_frm_offset == 4'd6) begin
                    ioc_frame_o         <= `AVMM_IDLE; //Send IDLE on the frame when no transaction.    
                end
                write_cmd_store         <= 1'b0;
                read_cmd_store          <= 1'b0;
                address_hold            <= 16'b0;
                writedata_hold          <= 16'b0;
                readdata_hold           <= 16'b0;
                crc_gen_clr             <= 1'b1; // clear crc initial value 
                tx_crc_cal              <= 1'b0;
                rx_crc_cal              <= 1'b0;
                crc_check_pass          <= 1'b1;
                
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0 && (ioc_frame_i == `AVMM_WR_REQ || ioc_frame_i == `AVMM_RD_REQ)) begin
					    status_reg           <= 4'd0;                    //clear status log for a new transaction
						 
						 rx_crc_cal           <= 1'b1;
                   write_cmd_store      <= ( ioc_frame_i == `AVMM_WR_REQ );
                   read_cmd_store       <= ( ioc_frame_i == `AVMM_RD_REQ );
                   state                <= RX_ADDR_B1;
					 end
					          
            end
                        
            RX_ADDR_B1: begin //store address, 2 bytes, MSB First
                crc_gen_clr        <= 1'b0;
                rx_crc_cal         <= 1'b1;
                
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0) begin
                   address_hold[15:8]   <= ioc_frame_i;
                   state                <= RX_ADDR_B0;
                end
            end
            
            RX_ADDR_B0: begin 
                rx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0) begin
                   address_hold[ 7:0]   <= ioc_frame_i;
                    
                   if (write_cmd_store) begin
                      state <= RX_DATA_B3;
                   end else begin
                      state <= CRC_CHECK;
                   end
                end
            end            
            
            RX_DATA_B3: begin
                rx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0) begin
                   writedata_hold[31:24] <= ioc_frame_i;
                   state                 <= RX_DATA_B2;
                end
            end
            
            RX_DATA_B2: begin
                rx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0) begin
                   writedata_hold[23:16] <= ioc_frame_i;
                   state                 <= RX_DATA_B1;
                end
            end
            
            RX_DATA_B1: begin
                rx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0) begin
                   writedata_hold[15: 8] <= ioc_frame_i;
                   state                 <= RX_DATA_B0;
                end
            end

            RX_DATA_B0: begin
                rx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0) begin
                   writedata_hold[ 7: 0] <= ioc_frame_i;
                   state                 <= CRC_CHECK;
                end
            end            
            
            CRC_CHECK: begin
				    if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (rx_frm_offset == 4'd0) begin                
                   rx_crc_cal           <= 1'b0;
                   tx_crc_cal           <= 1'b0;
                
                   crc_check_pass       <= ( ioc_frame_i == crc_out[7:0] );                
                   if (ioc_frame_i != crc_out[7:0] ) begin
			             status_reg        <= 4'd5;                   //log AVMM channel CRC check failure on CPU FPGA local			 
                      crc_err_cnt       <= crc_err_cnt + 1'b1;
                      state             <= CRC_FAIL_LOCAL;                    
                   end 
						 else begin                                      //CRC check pass, begin to assert AVMM_mst intf  
                      state             <= ASSERT_AVMM;
                   end
                end
            end
				
				CRC_FAIL_LOCAL: begin
				    tx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                    //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
                
                else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= `AVMM_CRC_ERR;           //send CRC_ERR indication to remote end
                   state                <= CRC_SEND;
                end
				end
            
            ASSERT_AVMM: begin //assert AVMM master bus for one clock cycle
				    crc_gen_clr             <= 1'b1;                    //Need to clear CRC data for next CRC data send
                mstr_agent_address      <= address_hold;
                mstr_agent_writedata    <= writedata_hold;
                
					 if (frame_loss_crc_H2A) begin
					    status_reg           <= 4'd2;                    //log H2A direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else begin
                   if(write_cmd_store) begin         
                      mstr_agent_read   <= 1'b0;
                      mstr_agent_write  <= 1'b1;               
                   end 
						 else begin
                      mstr_agent_read   <= 1'b1;
                      mstr_agent_write  <= 1'b0; 
                   end
                   state                <= PREPARE_CMPL;
					 end
            end
            
            PREPARE_CMPL: begin
                mstr_agent_address      <= 'b0;
                mstr_agent_read         <= 1'b0;
                mstr_agent_write        <= 1'b0;
                mstr_agent_writedata    <= 'b0;
					 
					 if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (avmm_timeout) begin
					    status_reg           <= 4'd8;                   //log AVMM channel timeout, 2ms for agent side
						 timeout_cnt          <= timeout_cnt + 1'b1;
                   state                <= IDLE;
					 end
					 
					 else if (invalid_access_cpu) begin
					    status_reg           <= 4'd6;                   //log invalid_access to CPU FPGA
						 crc_gen_clr          <= 1'b0;
                   state                <= INVALID_ACCESS_CPU;
					 end 
					 
					 else if (invalid_access_dbg) begin
					    status_reg           <= 4'd7;                   //log invalid_access to Debug FPGA
						 crc_gen_clr          <= 1'b0;
                   state                <= INVALID_ACCESS_DEBUG;
					 end
					 
					 else if (write_cmd_store && !mstr_agent_waitrequest) begin
					    crc_gen_clr          <= 1'b0;
                   state                <= TX_WR_CMPL;
					 end
					 
					 else if (read_cmd_store && mstr_agent_readdatavalid) begin
					    crc_gen_clr          <= 1'b0;
                   readdata_hold        <= mstr_agent_readdata;
                   state                <= TX_RD_CMPL;
					 end
                                
            end
				
				INVALID_ACCESS_CPU: begin
				    tx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= `INVALID_ACCESS_CPU;
                   state                <= CRC_SEND;
                end
				end
				
				INVALID_ACCESS_DEBUG: begin
				    tx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= `INVALID_ACCESS_DEBUG;
                   state                <= CRC_SEND;
                end 
				end
            
            TX_WR_CMPL: begin
                tx_crc_cal              <= 1'b1;
                
                if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= `AVMM_WR_CMPL;
                   state                <= CRC_SEND;
                end
                
            end
            
            TX_RD_CMPL: begin
                tx_crc_cal              <= 1'b1;
					 
                if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end 
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= `AVMM_RD_CMPL;
                   state                <= TX_DATA_B3;
                end
            end

            TX_DATA_B3: begin
                tx_crc_cal              <= 1'b1;
					 
					 if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= readdata_hold[31:24];
                   state                <= TX_DATA_B2;
                end
            end

            TX_DATA_B2: begin
                tx_crc_cal              <= 1'b1;
					 
                if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= readdata_hold[23:16];
                   state                <= TX_DATA_B1;
                end
            end            

            TX_DATA_B1: begin
                tx_crc_cal              <= 1'b1;
					 
                if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= readdata_hold[15: 8];
                   state                <= TX_DATA_B0;
                end
            end            

            TX_DATA_B0: begin
                tx_crc_cal              <= 1'b1;
					 
                if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   ioc_frame_o          <= readdata_hold[ 7: 0];
                   state                <= CRC_SEND;
                end
            end            
                    
            CRC_SEND: begin
					 
                if (frame_loss_crc_A2H || ~frame_aligned_tx) begin
					    status_reg           <= 4'd3;                   //log A2H direction frame_loss_crc error happened or link not aligned
					    state                <= IDLE;
					 end
					 
					 else if (tx_frm_offset == 4'd6) begin
                   tx_crc_cal           <= 1'b0;
                   ioc_frame_o          <= crc_out[7:0];  
                   state                <= IDLE;
                end
                
            end
            
            default: begin
                ioc_frame_o             <= `AVMM_IDLE;
                state                   <= IDLE;
            end
        endcase
    end //non reset    
end

//Watchdog Timer
always @ (posedge clk) begin
    if (reset) begin
        avmm_timer <= 16'h0;
    end else if ( state == IDLE ) begin
        avmm_timer <= 16'h0;
    end else if ( state == PREPARE_CMPL ) begin
        avmm_timer <= avmm_timer + 1'b1;
    end
end

assign avmm_timeout = avmm_timer == TIMEOUT_PARAM;

//CRC Generator_Checker
crc16_dat8 crc_gen_check
(
    .crc_in   ( crc_in      ),
    .dat_in   ( crc_data_in ), 
    .crc_out  ( crc_out     )
);

always @ (posedge clk)
  if (reset)
    crc_in <= 16'h0;
  else if (crc_gen_clr)
    crc_in <= 16'h0;
  else if ( (tx_crc_cal&(tx_frm_offset == 4'd9)) | (rx_crc_cal&(rx_frm_offset == 4'd2)) )
    crc_in <= crc_out;

always @ (posedge clk) begin
    if (reset) begin
        crc_data_in <= 8'h0;
    end else if (crc_gen_clr) begin
        crc_data_in <= 'h0;
    end else if ( rx_crc_cal&(rx_frm_offset == 4'd2) ) begin
        crc_data_in <= ioc_frame_i;
    end else if ( tx_crc_cal&(tx_frm_offset == 4'd9) ) begin
        crc_data_in <= ioc_frame_o;
    end else begin
        crc_data_in <= crc_data_in;
    end
end

endmodule    
